import { typeOf } from '@src/util/assist'
import Log from '@src/util/log.ts'
import { isString, isObject, isArray, isNull, isEmpty as TypeUtilIsEmpty } from '@src/util/type'

type ReplacerFunction = (this: any, key: string, value: any) => any
type ReviverFunction = (this: any, key: string, value: any) => any

/** 
 * @description 是否为空对象
*/
export function isEmpty(object: Record<string, any> = {}): boolean {
  return object && typeOf(object) === 'object' && Object.keys(object).length <= 0
}

/** 
 * @description 是否不为空对象
*/
export function isNotEmpty(object: Record<string, any> = {}): boolean {
  return isObject(object) && Object.keys(object).length > 0
}

/**
 * @description: 对象转字符串
 * @param {Object} 需要转换的对象
 * @param {ReplacerFunction} replacer
 * 该参数是一个函数，则在序列化过程中，被序列化的值的每个属性都会经过该函数的转换和处理；
 * @return {String} 转换后的字符串
 */
export function stringify(obj: Record<string, any> | any[] = {}, replacer: ReplacerFunction | null = null) {
  // 转换后的字符串
  let str: string = ''
  
  try {
    /** 
     * @description JSON.stringify()将值转换为相应的JSON格式：
     * 1. 转换值如果有 toJSON() 方法，该方法定义什么值将被序列化。
     * 2. 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
     * 3. 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
     * 4. undefined、任意的函数以及 symbol 值，在序列化过程中会被忽略（出现在非数组对象的属性值中时）或者被转换成 null（出现在数组中时）
     *    函数、undefined 被单独转换时，会返回 undefined，如JSON.stringify(function(){}) or JSON.stringify(undefined)。
     * 5. 对包含循环引用的对象（对象之间相互引用，形成无限循环）执行此方法，会抛出错误。
     * 6. 所有以 symbol 为属性键的属性都会被完全忽略掉，即便 replacer 参数中强制指定包含了它们。
     * 7. Date 日期调用了 toJSON() 将其转换为了 string 字符串（同Date.toISOString()），因此会被当做字符串处理。
     * 8. NaN 和 Infinity 格式的数值及 null 都会被当做 null。
     * 9. 其他类型的对象，包括 Map/Set/WeakMap/WeakSet，仅会序列化可枚举的属性。
    */
    // @ts-ignore 此处是因为 mdn文档和ts的类型不对应, 暂时以mdn为准
    str = JSON.stringify(obj, replacer)
    
  } catch (error) {
    str = isArray(obj) ? '[]' : isObject(obj) ? '{}' : String(obj)
    Log.error(error, stringify.name)
  }
  
  return str
}

/**
 * @description: 字符串转对象
 * @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
 * @param {string} 需要转换的字符串
 * @param {ReviverFunction} reviver
 * @return {Record<string, any>} 转换后的对象
 */
export function parse(str: string, reviver: ReviverFunction | undefined = undefined) {
  
  // 转换后的对象
  let obj: Record<string, any> = {}
  
  try {
    
    obj = JSON.parse(str, reviver)
    
  } catch (error) {
    
    Log.error(error, parse.name)
    
    obj = {}
  }
  
  return obj
}

/**
 * @description: 字符串转对象
 * @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
 * @param {string} 需要转换的字符串
 * @param {ReviverFunction} reviver
 * @return {Record<string, any>} 转换后的对象
 */
export function parse_with_default_value<T extends Record<string, any>>(
  str: string, 
  defaultValue: T, 
  reviver?: ReviverFunction
) {
  
  // 转换后的对象
  let obj: T = {} as T
  
  if (isNull(str) || TypeUtilIsEmpty(str)) {
    return defaultValue || {} as T
  }
  
  try {
    
    obj = JSON.parse(str, reviver)
    
  } catch (error) {
    
    Log.error(error, parse.name)
    
    obj = defaultValue || {}
  }
  
  return obj
}

export function parseObjectHandler(value: any, defaultValue: any = {}): any {
  // 是否为数组
  if (isArray(value)) return value.map((item: any) => parseObjectHandler(item))
  // 是否字符串
  if (isString(value)) {
    try {
      return JSON.parse(value)
    } catch (error) {
      return value
    }
  }
  // 是否是对象
  if (!isObject(value)) return value
  
  let newValue: any = {}
  
  for(let key in value) {
    let item = value[key]
    newValue[key] = parseObjectHandler(item)
  }
  
  return newValue
}

/** 
 * @description 解析对象
*/
export function parseObject(value: any, defaultValue: any = {}): any {
  // 是否为数组
  if (isArray(value)) return value.map((item: any) => parseObjectHandler(item))
  // 是否字符串
  if (isString(value)) {
    try {
      return JSON.parse(value)
    } catch (error) {
      return defaultValue || value
    }
  }
  // 是否是对象
  if (!isObject(value)) return value
  
  let newValue: any = {}
  
  for(let key in value) {
    let item = value[key]
    newValue[key] = parseObjectHandler(item)
  }
  
  return newValue
}

const hasOwnProperty = Object.prototype.hasOwnProperty
export function hasOwn (obj: Record<string, any> | any[], key: string): boolean {
  return hasOwnProperty.call(obj, key)
}

export function filterProperty (obj: Record<string, any>, keys: string[]):  Record<string, any> {
  let filterObj: Record<string, any> = {}
  
  Object.keys(obj).forEach(key => {
    if (keys.includes(key)) return
    
    filterObj[key] = obj[key]
  })
  
  return filterObj
}

export function parseJsonToObject<T extends Record<string, any>>(data: string): T {
  try {
    return JSON.parse(data) as T
  } catch (error) {
    Log.error(error, parseJsonToObject.name)
    return {} as T
  }
}

export function isJSONObject(data: string): boolean {
  try {
    const result = JSON.parse(data)
    return isObject(result)
  } catch (error) {
    return false
  }
}

export default {
  isEmpty,
  parseObject,
  parseJsonToObject
}